#include "sde.h"

diff_layer* gen_initial_seq(FILE* file) {
    char line[BUF_MAX], *line_p, *ending_p;
    double temp_seq[BUF_MAX];
    size_t size = -1;

    /* scan individual lines */
    while (fgets(line, sizeof(line), file)) {
        /* set initial line_p pointer to line */
        /* increment by setting line_p to last ending_p */
        for (line_p = line;; line_p = ending_p) {
            /* pass str to strtol to generate long, setting ending_p to point */
            /* at the character after the last digit of this long */
            temp_seq[++size] = strtod(line_p, &ending_p);
            /* if line pointer matches ending pointer */
            if (line_p == ending_p)
                break;
        }
    }

    if (size < 3) {
        fverbose(stderr, "sdiffengine: two values sequence is too ambiguous %li\n", size);
        exit(ERR_SEQUENCE_EXHAUSTED);
    }

    double *seq = malloc(sizeof(double) * size);
    for (int i = 0; i < size; i++) {
        verbose("%f ", temp_seq[i]);
        seq[i] = temp_seq[i];
    }

    return create_diff_layer(seq, size, ROOT_LAYER, gen_type(NULL, ROOT_TYPE));
}

/* takes init contents of a diff_layer and initializes the data structure */
diff_layer* create_diff_layer(double *seq, size_t size, int depth, char *type) {
    diff_layer *dl = malloc(sizeof(diff_layer));
    dl->a_delta = NULL;
    dl->m_delta = NULL;
    dl->seq = seq;
    dl->size = size;
    dl->depth = depth;
    dl->type = type;
    dl->consistency = INCONSISTENT;
    return dl;
}

/* destroys (frees) contents of as well as diff_layer */
void destroy_diff_layer(diff_layer *dl) {
    if (dl == NULL)
        return;

    destroy_diff_layer(dl->a_delta);
    destroy_diff_layer(dl->m_delta);

    free(dl->type);
    free(dl->seq);
    free(dl);
}

/* helper function checks surreal values */
/* returns (int) consistency it received, assuming it was not invalid */
int validate_consistency(diff_layer *dl, int consistency) {
    if (consistency == CONSISTENT)
        if (dl->seq[0] == INFINITY)
            consistency = INCONSISTENT;

    dl->consistency = consistency;
    return consistency;
}

/* helper function to generate type strings */
/* generates a (char*) with max length of (LAYERS) */
/* takes existing type and appends a new type */
/* (char*) prev_type can be NULL */
/* returns (char*) type string */
char *gen_type(char *prev_type, char type) {
    char *new_type = malloc(sizeof(char) * LAYERS);

    int i = 0;
    /* on type strings for nodes that aren't children, prev_type is NULL */
    if (prev_type != NULL) {
        do {
            new_type[i] = prev_type[i];
        } while (prev_type[++i] != '\0');
    }

    new_type[i++] = type;
    new_type[i] = '\0';

    return new_type;
}

/* generate add and mult diffs simultaneously */
/* returns (int) error or success code taken from def.h */
int gen_diffs(diff_layer *dl) {
    /* the amount of computable diffs falls of by one for each */
    /* layer we go down */
    /* no trustworthy sequence can be made from under 3 values */
    if ((dl->size - dl->depth) < 3) return ERR_SEQUENCE_EXHAUSTED;

    /* new seq initalization */
    double *a_diff_seq = malloc(sizeof(double) * dl->size);
    double *m_diff_seq = malloc(sizeof(double) * dl->size);

    /* keep track of consistency */
    int a_consistency = CONSISTENT, m_consistency = CONSISTENT;
    /* precompute first result to avoid runtime check along the lines */
    /* of 'if (i == 0) continue;' */
    a_diff_seq[0] = dl->seq[1] - dl->seq[0];
    m_diff_seq[0] = dl->seq[1] / dl->seq[0];
    for (int i = 1; i < (dl->size - dl->depth - 1); i++) {
        /* compute next diffs */
        a_diff_seq[i] = dl->seq[i + 1] - dl->seq[i];
        m_diff_seq[i] = dl->seq[i + 1] / dl->seq[i];

        /* do comparison during generation by setting consistency */
        /* if values don't match, disprove the seq's consistency */
        if (a_diff_seq[i] != a_diff_seq[i - 1])
            a_consistency = INCONSISTENT;
        if (m_diff_seq[i] != m_diff_seq[i - 1])
            m_consistency = INCONSISTENT;
    }

    /* create next diff layers */
    dl->a_delta = create_diff_layer(a_diff_seq, dl->size, dl->depth + 1, gen_type(dl->type, ADD_TYPE));
    dl->m_delta = create_diff_layer(m_diff_seq, dl->size, dl->depth + 1, gen_type(dl->type, MUL_TYPE));

    if (validate_consistency(dl->a_delta, a_consistency) ||
        validate_consistency(dl->m_delta, m_consistency))
        return SOLUTION_FOUND;

    return NOTHING_FOUND;
}

/* takes a diff_layer considered root and generates all subsequent */
/* diff sequences, until either */
/* - a solution is found => return (int) success code */
/* - sequence is exhausted => return (int) error code */
/* - max layer is reached  => return (int) error code */
/* all return values defined in def.h */
int gen_all_diffs(diff_layer *dl) {
    /* keep it reasonable */
    if (dl->depth > LAYERS)
        return ERR_MAX_DEPTH_REACHED;

    /* generate thy own sequences */
    int ret = gen_diffs(dl);

    if (ret == ERR_SEQUENCE_EXHAUSTED) {
        verbose("sdiffengine: sequence path exhausted @ layer %i\n", dl->depth + 1);
        return ERR_SEQUENCE_EXHAUSTED;
    }

    /* debugging output */
    print_diff_layer(dl->a_delta);
    print_diff_layer(dl->m_delta);

    if (ret == SOLUTION_FOUND) {
        verbose("sdiffengine: sequence continuation found @ layer %i\n", dl->depth + 1);
        return SOLUTION_FOUND;
    }

    /* bringeth forth thy children */
    if (gen_all_diffs(dl->a_delta) == SOLUTION_FOUND ||
        gen_all_diffs(dl->m_delta) == SOLUTION_FOUND)
        return SOLUTION_FOUND;

    return NOTHING_FOUND;
}

/* continue sequence based on its diff sequence */
void continue_seq(diff_layer* dl, diff_layer* cont_dl, size_t n, double (*operation)(double,double)) {
    double *long_seq = malloc(sizeof(double) * n);
    double prev = .0, d_prev = .0;

    long_seq[0] = dl->seq[0];
    for (int i = 1; i < n; i++) {
        if (i < dl->size - dl->depth) {
            long_seq[i] = dl->seq[i];
            continue;
        }

        prev = long_seq[i - 1];
        if (cont_dl != NULL)
            d_prev = cont_dl->seq[i - 1];

        long_seq[i] = (*operation)(prev, d_prev);
    }

    free(dl->seq);
    dl->seq = long_seq;

    dl->size = n + dl->depth;
}

/* copy operation for function pointer use */
double copy_prev(double prev, double d_prev) {
    return prev;
}

/* addition operation for function pointer use */
double add_prev(double prev, double d_prev) {
    return prev + d_prev;
}

/* multiply operation for function pointer use */
double multiply_prev(double prev, double d_prev) {
    return prev * d_prev;
}

/* generates the next double value of a sequence */
void gen_next(diff_layer *dl, double *next) {
    /* in true recursive function fashion */
    if (dl == NULL)
        return;

    /* bubble up calculated result */
    if (dl->consistency == CONSISTENT) {
        *next = dl->seq[dl->size - dl->depth - 1];
        return;
    }

    double a_next = .0;
    double m_next = .0;

    gen_next(dl->a_delta, &a_next);
    gen_next(dl->m_delta, &m_next);

    if (a_next != .0) {
        *next = dl->seq[dl->size - dl->depth - 1] + a_next;
        return;
    }

    if (m_next != .0) {
        *next = dl->seq[dl->size - dl->depth - 1] * m_next;
        return;
    }
}

/* generate continuation of input sequence with length n */
int gen_next_n(diff_layer *dl, size_t n) {
    /* in true recursive function fashion */
    if (dl == NULL)
        return NOTHING_FOUND;

    double (*operation)(double, double);

    if (dl->consistency == CONSISTENT) {
        operation = &copy_prev;
        continue_seq(dl, NULL, n, operation);
        print_diff_layer(dl);
        return SOLUTION_FOUND;
    }

    if (gen_next_n(dl->a_delta, n) == SOLUTION_FOUND) {
        operation = &add_prev;
        continue_seq(dl, dl->a_delta, n, operation);
        print_diff_layer(dl);
        return SOLUTION_FOUND;
    }

    if (gen_next_n(dl->m_delta, n) == SOLUTION_FOUND) {
        operation = &multiply_prev;
        continue_seq(dl, dl->m_delta, n, operation);
        print_diff_layer(dl);
        return SOLUTION_FOUND;
    }

    return NOTHING_FOUND;
}
